package com.example.jdk8.lambdaStream;

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Java8Test2 {

    public static void main(String[] args) {

       /* Stream是元素的集合，这点让Stream看起来用些类似Iterator；
        可以支持顺序和并行的对原Stream进行汇聚的操作；


        大家可以把Stream当成一个高级版本的Iterator。原始版本的Iterator，用户只能一个一个的遍历元素并对其执行某些操作；
        高级版本的Stream，用户只要给出需要对其包含的元素执行什么操作，比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等，
        具体这些操作如何应用到每个元素上，就给Stream就好了！（这个秘籍，一般人我不告诉他：））


        创建Stream；
        转换Stream，每次转换原有Stream对象不改变，返回一个新的Stream对象（**可以有多次转换**）；
        对Stream进行聚合（Reduce）操作，获取想要的结果；
        */




       /* 2. 创建Stream
        最常用的创建Stream有两种途径：

        1 通过Stream接口的静态工厂方法（注意：Java8里接口可以带静态方法）；
        2 通过Collection接口的默认方法（默认方法：Default method，也是Java8中的一个新特性，
        就是接口中的一个带有实现的方法，后续文章会有介绍）–stream()，把一个Collection对象转换成Stream*/

       /* 2.1 使用Stream静态方法来创建Stream
        1. of方法：有两个overload方法，一个接受变长参数，一个接口单一值*/

        Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
        Stream<String> stringStream = Stream.of("taobao");

        /*2. generator方法：生成一个无限长度的Stream，其元素的生成是通过给定的Supplier
        （这个接口可以看成一个对象的工厂，每次调用返回一个给定类型的对象）

        三条语句的作用都是一样的，只是使用了lambda表达式和方法引用的语法来简化代码。
        每条语句其实都是生成一个无限长度的Stream，其中值是随机的。这个无限长度Stream是懒加载，
        一般这种无限长度的Stream都会配合Stream的limit()方法来用。
        */

//        Stream.generate(new Supplier<Double>() {
//            @Override
//            public Double get() {
//                return Math.random();
//            }
//        });
        Stream.generate(() -> Math.random());
        Stream.generate(Math::random);


        /*3. iterate方法：也是生成无限长度的Stream，和generator不同的是，其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。
        其中包含的元素可以认为是：seed，f(seed),f(f(seed))无限循环

        这段代码就是先获取一个无限长度的正整数集合的Stream，然后取出前10个打印。千万记住使用limit方法，不然会无限打印下去。
        */
        Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);


     /*   2.2 通过Collection子类获取Stream
        这个在本文的第一个例子中就展示了从List对象获取其对应的Stream对象，
        如果查看Java doc就可以发现Collection接口有一个stream方法，所以其所有子类都都可以获取对应的Stream对象。*/

       /* 转换Stream
        转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。Stream接口中定义了几个常用的转换方法，下面我们挑选几个常用的转换方法来解释。
        1. distinct: 对于Stream中包含的元素进行去重操作（去重逻辑依赖元素的equals方法），新生成的Stream中没有重复的元素；

        distinct方法示意图(**以下所有的示意图都要感谢[RxJava](https://github.com/Netflix/RxJava)项目的doc中的图片给予的灵感, 如果示意图表达的有错误和不准确的地方，请直接联系我。**)：

        2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作，新生成的Stream只包含符合条件的元素；

        filter方法示意图：

        3. map: 对于Stream中包含的元素使用给定的转换函数进行转换操作，新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法，分别是：mapToInt，mapToLong和mapToDouble。这三个方法也比较好理解，比如mapToInt就是把原始Stream转换成一个新的Stream，这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法，可以免除自动装箱/拆箱的额外消耗；

        map方法示意图：

        4. flatMap：和map类似，不同的是其每个元素转换得到的是Stream对象，会把子Stream中的元素压缩到父集合中；

        flatMap方法示意图：

        5. peek: 生成一个包含原Stream的所有元素的新Stream，同时会提供一个消费函数（Consumer实例），新Stream每个元素被消费的时候都会执行给定的消费函数；

        peek方法示意图：

        6. limit: 对一个Stream进行截断操作，获取其前N个元素，如果原Stream中包含的元素个数小于N，那就获取其所有的元素；

        limit方法示意图：

        7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream，如果原Stream中包含的元素个数小于N，那么返回空Stream；

        skip方法示意图：

        8. 在一起,在一起！*/


        String[] str ={"ee","bb","dd","bb"};
        List<String> names = Arrays.asList(str);
        List<String> disnames=names.stream().distinct().collect(Collectors.toList());
        System.out.println(disnames);

//        List<String> mapNames= names.stream().map( String:: toLowerCase).collect(Collectors.toList());
//        System.out.println(mapNames);

//        List<String> flatmapNames= names.stream().flatMap(String:: toLowerCase).collect(Collectors.toList());

        Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
        List<Integer> nums2 = Lists.newArrayList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10);
        System.out.println("sum is:" + nums2.stream().filter(num -> num != null).
                distinct().mapToInt(num -> num * 2).
                peek(System.out::println).skip(2).limit(4).sum()); // 36
//        ((3+4+5+6 )* 2)
        //peek ???

        List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
        List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
                collect(() -> new ArrayList<Integer>(),
                        (list, item) -> list.add(item),
                        (list1, list2) -> list1.addAll(list2));



     /*   reduce方法接受一个函数，这个函数有两个参数，
            第一个参数是上次函数执行的返回值（也称为中间结果）  ，第二个参数是stream中的元素，
             这个函数把这两个值相加，得到的和会被赋值给下次执行这个函数的第一个参数。
             要注意的是：**第一次执行的时候第一个参数的值是Stream的第一个元素，第二个参数是Stream的第二个元素**
        。这个方法返回值类型是Optional，这是Java8防止出现NPE的一种可行方法，
        后面的文章会详细介绍，这里就简单的认为是一个容器，其中可能会包含0个或者1个对象。
        */
        List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
        System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());


       /* reduce方法还有一个很常用的变种：
        T reduce(T identity, BinaryOperator<T> accumulator);
        这个定义上上面已经介绍过的基本一致，不同的是：它允许用户提供一个循环计算的初始值，
        如果Stream为空，就直接返回该值。而且这个方法不会返回Optional，因为其不会出现null值。下面直接给出例子，就不再做说明了。*/

        System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));

        System.out.println("ints count is:" + ints.stream().count());

        /* 搜索相关
        – allMatch：是不是Stream中的所有元素都满足给定的匹配条件
        – anyMatch：Stream中是否存在任何一个元素满足匹配条件
        – findFirst: 返回Stream中的第一个元素，如果Stream为空，返回空Optional
        – noneMatch：是不是Stream中的所有元素都不满足给定的匹配条件
        – max和min：使用给定的比较器（Operator），返回Stream中的最大|最小值
          下面给出allMatch和max的例子，剩下的方法读者当成练习。*/

        System.out.println("allMatch->" + ints.stream().allMatch( item -> item<10));
        System.out.println("anyMatch->" + ints.stream().anyMatch( item -> item<10));
        System.out.println("noneMatch->" + ints.stream().noneMatch( item -> item>100));
        System.out.println("findFirst->" + ints.stream().findFirst().get());

        ints.stream().max( (a1,b1) -> a1.compareTo(b1)).ifPresent(System.out::println);
//        ints.stream().sorted();
//        Collections.sort(names, (o1, o2) -> o1.compareTo(o2));
//        System.out.println(names);

    }
}
